﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Globalization;

namespace HgCo.WindowsLive.SkyDrive.Support
{
    /// <summary>
    /// Provides methods for parsing WebResponse's Headers.
    /// </summary>
    internal class WebResponseHelper
    {
        #region Fields
        
        /// <summary>
        /// The regular expression to parse Set-Cookie's part Domain.
        /// </summary>
        private readonly Regex RegexCookiePartDomain =
            new Regex(@"^(?i:\s?(?<Name>domain)=?(\s|(?<Value>[^\s].*)))$");
        
        /// <summary>
        /// The regular expression to parse Set-Cookie's part Path.
        /// </summary>
        private readonly Regex RegexCookiePartPath =
            new Regex(@"^(?i:\s?(?<Name>path)=?(\s|(?<Value>[^\s].*)))$");
        
        /// <summary>
        /// The regular expression to parse Set-Cookie's part Expires.
        /// </summary>
        private readonly Regex RegexCookiePartExpires =
            new Regex(@"^(?i:\s?(?<Name>expires)=?(\s|(?<Value>[^\s].*)))$");

        /// <summary>
        /// The regular expression to parse Set-Cookie's part HttpOnly.
        /// </summary>
        private readonly Regex RegexCookiePartHttpOnly =
            new Regex(@"^(?i:\s?(?<Name>HTTPOnly)=?(\s|(?<Value>[^\s].*)))$");

        /// <summary>
        /// The regular expression to parse Set-Cookie's part Secure.
        /// </summary>
        private readonly Regex RegexCookiePartSecure =
            new Regex(@"^(?i:\s?(?<Name>secure)=?(\s|(?<Value>[^\s].*)))$");

        /// <summary>
        /// The regular expression to parse Set-Cookie's part Version.
        /// </summary>
        private readonly Regex RegexCookiePartVersion =
            new Regex(@"^(?i:\s?(?<Name>version)=?(\s|(?<Value>[^\s].*)))$");

        /// <summary>
        /// The regular expression to parse Set-Cookie's part Name and Value.
        /// </summary>
        private readonly Regex RegexCookiePartNameValue =
            new Regex(@"^((?<Name>[^=]+)=)?(\s|(\s*(?<Value>.*)))$");

        #endregion

        #region Methods

        /// <summary>
        /// Parses the cookies from the given webresponse's header.
        /// </summary>
        /// <param name="webResponse">The web response to parse.</param>
        /// <returns>The list of cookies.</returns>
        public Cookie[] ParseCookies(WebResponse webResponse)
        {
            List<Cookie> lCookie = new List<Cookie>();
            if (webResponse != null &&
                !String.IsNullOrEmpty(webResponse.Headers["Set-Cookie"]))
            {
                string[] responseCookies = webResponse.Headers["Set-Cookie"].Split(',');
                for (int idx = 0; idx < responseCookies.Length; idx++)
                {
                    string responseCookie = responseCookies[idx];
                    bool isCookieBroken = false;
                    foreach (string dayOfWeekName in Enum.GetNames(typeof(DayOfWeek)))
                    {
                        Regex regexCookieBroken = new Regex(String.Format(
                            CultureInfo.InvariantCulture, 
                            "(?i:expires\\s*=\\s*{0}\\s*)$",
                            dayOfWeekName.Substring(0, 3)));
                        if (regexCookieBroken.IsMatch(responseCookie))
                        {
                            isCookieBroken = true;
                            break;
                        }
                    }
                    if (isCookieBroken)
                    {
                        responseCookie += responseCookies[idx + 1];
                        idx++;
                    }
                    Cookie cookie = ParseCookie(responseCookie);
                    if (String.IsNullOrEmpty(cookie.Domain))
                        cookie.Domain = webResponse.ResponseUri.Authority;
                    lCookie.Add(cookie);
                }
            }
            return lCookie.ToArray();
        }

        /// <summary>
        /// Parses a cookie from Set-Cookie string.
        /// </summary>
        /// <param name="setCookie">The Set-Cookie string.</param>
        /// <returns>The parsed cookie.</returns>
        public Cookie ParseCookie(string setCookie)
        {
            Cookie cookie = new Cookie() 
            { 
                Path = "/", 
                Expired = false,
                Expires = DateTime.MaxValue
            };
            string[] parts = setCookie.Split(';');
            string partNameValue = String.Empty;

            for (int idxPart = parts.Length - 1; idxPart >= 0; idxPart--)
            {
                string part = parts[idxPart];
                if (!String.IsNullOrEmpty(part))
                {
                    if (RegexCookiePartDomain.IsMatch(part))
                    {
                        string domain = RegexCookiePartDomain.Match(part).Groups["Value"].Value;
                        cookie.Domain = domain;
                    }
                    else if (RegexCookiePartPath.IsMatch(part))
                    {
                        string path = RegexCookiePartPath.Match(part).Groups["Value"].Value;
                        cookie.Path = path;
                    }
                    else if (RegexCookiePartExpires.IsMatch(part))
                        try
                        {
                            string expires = RegexCookiePartExpires.Match(part).Groups["Value"].Value;
                            if (!String.IsNullOrEmpty(expires))
                                cookie.Expires = DateTime.Parse(expires, CultureInfo.InvariantCulture);
                        }
                        catch (FormatException) { }
                    else if (RegexCookiePartHttpOnly.IsMatch(part))
                    {
                        string httpOnly = RegexCookiePartHttpOnly.Match(part).Groups["Value"].Value;
                        if (!String.IsNullOrEmpty(httpOnly))
                            cookie.HttpOnly = Boolean.Parse(httpOnly);
                    }
                    else if (RegexCookiePartSecure.IsMatch(part))
                    {
                        string secure = RegexCookiePartSecure.Match(part).Groups["Value"].Value;
                        if (!String.IsNullOrEmpty(secure))
                            cookie.Secure = Boolean.Parse(secure);
                    }
                    else if (RegexCookiePartVersion.IsMatch(part))
                    {
                        string version = RegexCookiePartVersion.Match(part).Groups["Value"].Value;
                        if (!String.IsNullOrEmpty(version))
                            cookie.Version = Int32.Parse(version, CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        partNameValue = String.Format(
                            CultureInfo.InvariantCulture, 
                            "{0}{1}", 
                            part, 
                            partNameValue);
                    }
                }
            }
            if (RegexCookiePartNameValue.IsMatch(partNameValue))
            {
                Match matchCookieNameValue = RegexCookiePartNameValue.Match(partNameValue);
                string name = matchCookieNameValue.Groups["Name"].Value;
                string value = matchCookieNameValue.Groups["Value"].Value;
                if (!String.IsNullOrEmpty(name))
                    cookie.Name = name;
                if (!String.IsNullOrEmpty(value))
                    cookie.Value = value;
            }

            return cookie;
        }

        #endregion
    }
}
